import { AbilityBuilder, createMongoAbility, ExtractSubjectType, Subject } from '@casl/ability'
import { Injectable } from '@nestjs/common'
import { UserService } from '../user/user.service'
import { getEntities } from '../utils/common'
import { Menus } from '../menus/menus.entity'

@Injectable()
export class CaslAbilityService {
  constructor(private userService: UserService) {}
  //createForUser() 是为每个用户动态计算权限的方法，非常适合基于用户的身份、角色、行为等信息来定制权限。
  //forRoot() 是一种确保全局配置、服务只初始化一次并且共享给整个应用的机制
  // nest官方使用的是reateForUser()比较语义化，是为了用户去创建一套关于策略的权限控制的工厂函数，
  // 而我们使用ForRoot，因为我们是针对整个系统的
  async forRoot(username: string) {
    const { can, build } = new AbilityBuilder(createMongoAbility)
    // can('manage', 'all')
    // 跟实体对应
    //menu 名称、路径，acl => actions =>名称、路径 => 实体对应
    //path => prefix => 写死在项目里
    // 有了实体和acl的数据，遍历就能获得用户的ability

    // 其他思路：acl => 表来进行存储 => LogController + Action
    // log => sys:log => sys:log:read,sys:log:write
    const user = await this.userService.find(username)
    // user => 1:n roles =>1:n menus =>去重 {}
    const obj = {} as Record<string, unknown>
    user?.roles.forEach((o) => {
      o.menus.forEach((menu) => {
        // path -> acl -> actions
        // 通过Id去重
        obj[menu.id] = menu
      })
    })
    const menus = Object.values(obj) as Menus[]
    menus.forEach((menu) => {
      const actions = menu.acl.split(',')
      for (let i = 0; i < actions.length; i++) {
        const action = actions[i]
        const subject = getEntities(menu.path)
        if (subject) {
          can(action, subject)
        }
      }
    })
    // can('read', Logs)
    // can('update', Logs)
    const ability = build({
      detectSubjectType: (object) => object.constructor as ExtractSubjectType<Subject>
    })
    //这里只是返回一个ability，还没有去验证：ability.can()
    // ability.can('manage', 'all')
    // 这里因为不知道具体传递的参数是什么，所以要把参数传递的权限交出来->控制反转
    // 我们不知道用户到底要用can/cannot来去判断当前ability有没有去访问某个实体的权限，
    // 然后我们这边直接把这个函数的权利交出来，让用户自己来定义
    // 比如说：写一个@checkPolicies((ability)=>ability.cannot(Action,User,['']))
    // 可以随意定义can还是cannot，随意更换操作和实体之类的,最终得到一个build数据，正好对应拦截器，true可以访问，false不能访问
    return ability //相当于把ability创建完成
  }
}
